import { extend, hasChanged, hasOwn, isArray, isIntegerKey, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
import { reactive, readonly } from "./reactive";

/**
 * 
 * @param isReadonly 是否只读
 * @param shallow 是否深入劫持
 * @returns 
 */
function createGetter(isReadonly = false, shallow = false) {

  return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver);
    
    if(!isReadonly) {
      // console.log('effect 回调函数中的响应式数据’取值‘式，收集属性对应的 effect')
      // 收集依赖，数据变化更新视图
      track(target, TrackOpTypes.GET, key); // 开始收集依赖
    }

    if (shallow) {
      return res;
    }

    // vue2 一上来就递归劫持数据，vue3 是当取值时才去劫持代理数据（懒代理）
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }

    return res;
  }
}

/**
 * 
 * @param shallow 是否深入劫持
 * @returns 
 */
function createSetter(shallow = false) {
  // 返回 set 
  return (target, property, value, receiver) => {
    
    const oldValue = target[property]; // 获取旧值,必须在 Reflect.set 之前获取
    // 当用户重新更新属性值时, 通知对应的属性的 effect 函数执行
    // 判断属性是新增还是已经存在的需要修改值
    // 判断是数组,且是索引,表明不是在数组中新增
    const hasProperty = isArray(target) && isIntegerKey(property) ? Number(property) < target.length : hasOwn(target, property);
    
    const res = Reflect.set(target, property, value, receiver);
    
    if (!hasProperty) {
      // 表示是在数组中添加数据
      trigger(target, TriggerOrTypes.ADD, property, value, oldValue); // 数据变化了, 更新视图
    } else if (hasChanged(oldValue, value)) {
      // 修改数据
      trigger(target, TriggerOrTypes.SET, property, value, oldValue); // 数据变化了, 更新视图
    }
    
    return res;
  }
}

const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const showllReadonlyGet = createGetter(true, true);

const set = createSetter();
const shallowSet = createSetter(true);

export const mutableHandlers = {
  get,
  set
};
export const shallowReactiveHandlers = {
  get: shallowGet,
  set: shallowSet
};

const readonlyObj = {
  set: (target, key) => {
    console.log(`set on property ${key} falied`);
  }
}
export const readonlyHandlers = extend({
  get: readonlyGet
}, readonlyObj);
export const shallowReadonlyHandlers = extend({
  get: showllReadonlyGet
}, readonlyObj);